﻿using System;
using System.Text;
using System.Collections;
using System.Collections.Generic;
using Checkers.Common;

namespace Checkers.Game
{
    /// <summary>
    /// This class is the engine of a single game
    /// </summary>
    public class CheckersGame
    {
        // Data members and consts
        private const int k_DefaultBoardSize = 8;
        private GameMove m_LastMove;
        private GameMove m_LastBurntMove;
        private GameData m_GameData;
        public static readonly ArrayList K_LegitGameSizes = new ArrayList();

        // Ctors
        static CheckersGame()
        {
            K_LegitGameSizes.Add(6);
            K_LegitGameSizes.Add(8);
            K_LegitGameSizes.Add(10);
        }

        public CheckersGame()
            : this(k_DefaultBoardSize, eGameType.HumanVsHuman)
        {
        }

        public CheckersGame(int i_BoardSize, eGameType i_GameType)
        {
            // Init the game bu using the user components
            m_GameData = new GameData();
            initGame(i_BoardSize, i_GameType);
        }

        #region Properties

        // Properties
        public GameMove LastMove
        {
            get
            {
                return m_LastMove;
            }
        }

        public GameMove LastBurntMove
        {
            get
            {
                return m_LastBurntMove;
            }
        }

        public GameData GameData
        {
            get
            {
                return m_GameData;
            }
        }

        #endregion

        // Methods

        /// <summary>
        /// Initializes a new game
        /// </summary>
        /// <param name="i_BoardSize">The size of the game's board</param>
        /// <param name="i_GameType">The type of the game</param>
        private void initGame(int i_BoardSize, eGameType i_GameType)
        {
            // Init the game type
            m_GameData.GameType = i_GameType;
            m_GameData.GameBoardSize = i_BoardSize;

            // Init the players of the game
            initPlayers(i_GameType);
        }

        /// <summary>
        /// Start a new single game. If a game was played everything is reset.
        /// </summary>
        /// <param name="i_BoardSize"></param>
        public void StartSingleGame()
        {
            // Activate the game by setting it's status to be active
            m_GameData.GameStatus = eGameStatus.Active;

            // Create a new board by using the ctor in class GameBoard
            m_GameData.GameBoard = new GameBoard(m_GameData.GameBoardSize);
        }

        /// <summary>
        /// Run a single turn
        /// </summary>
        /// <param name="i_CurrentPlayerMove"></param>
        public bool RunASingleTurn(GameMove i_CurrentPlayerMove)
        {
            bool isMoveLegal = isLegalMove(i_CurrentPlayerMove);
            GameMove tempJump;

            // Check if the current move is legal         
            if (isMoveLegal)
            {
                // If its not a jump - check if another move was a possible jump
                if (i_CurrentPlayerMove.ChekMoveType() == eMoveType.RegularMove && couldPlayerJumpInstead(i_CurrentPlayerMove))
                {
                    // Give the player his panelty ("burn" his pawn) and dont rule out the move
                    m_GameData.GameBoard[LastBurntMove.SourceCellLoc] = null;
                }
                else
                {
                    // Matke the move
                    makeMove(i_CurrentPlayerMove);
                }

                // Save the last move
                m_LastMove = i_CurrentPlayerMove;

                // change the current player unless another jump is possible
                if (LastMove.ChekMoveType() != eMoveType.Jump || !tryJump(LastMove.DestCellLocation, out tempJump))
                {
                    changeTheCurrPlayer();
                    
                    // Update game status and score            
                    updateGameStatus();
                    updateGameScore();
                }
            }

            return isMoveLegal;
        }

        /// <summary>
        /// Updates the score of the game and updates the game turn
        /// </summary>
        private void updateGameScore()
        {
            // Check the status of the game 
            switch (m_GameData.GameStatus)
            {
                case eGameStatus.Forfeit:

                    // The player forfeit - give a point to the other player
                    if (m_GameData.CurrentPlayer == m_GameData.Player1)
                    {
                        m_GameData.Player2.PlayerScore++;
                    }
                    else
                    {
                        m_GameData.Player1.PlayerScore++;
                    }

                    break;

                case eGameStatus.Win:

                    // Give the winner a point
                    m_GameData.WinnerPlayer.PlayerScore++;
                    break;

                default:
                    break;
            }
        }

        /// <summary>
        /// This method update the member m_GameStatus according to the game state
        /// </summary>
        private void updateGameStatus()
        {
            if (m_GameData.GameBoard.BlackPawns == 0 || m_GameData.GameBoard.WhitePawns == 0)
            {
                m_GameData.GameStatus = eGameStatus.Win;
            }
            else 
            {
                // Check if the current player has a move to make
                if (!hasValidMoves())
                {
                    // Change the current player
                    changeTheCurrPlayer();

                    // Check if the current player has a move to make
                    if (!hasValidMoves())
                    {
                        // Change the status of the game to be a tie
                        m_GameData.GameStatus = eGameStatus.Tie;
                    }
                }
            }
        }

        /// <summary>
        /// This method checks if the current player has any valid moves
        /// </summary>
        /// <returns>True if it has and false if not</returns>
        private bool hasValidMoves()
        {
            // Variables
            bool v_IsATie = true;
            Location sourceLoc;
            GameMove currMove = null;
            List<Location> destLocs;

            // Run on the board and check the possible moves
            for (int row = 1; row <= m_GameData.GameBoard.Height && v_IsATie; row++)
            {
                for (int col = 1; col <= m_GameData.GameBoard.Width && v_IsATie; col++)
                {
                    // Create a new location
                    sourceLoc = new Location(row, col);

                    // Get the possible destination locatoins
                    destLocs = getThePossibleDestLocs(sourceLoc);

                    // Run on each destination and cjeck if it makes a legal move
                    // if one of them is true return true
                    foreach (Location currLocation in destLocs)
                    {
                        currMove = new GameMove(sourceLoc, currLocation);

                        // Check if the current moves list is legal
                        if (isLegalMove(currMove))
                        {
                            v_IsATie = false;
                        }
                    }
                }
            }

            // If one of the moves is legal return true
            return !v_IsATie;
        }

        /// <summary>
        /// Return all the locations that the player can go to them in the board
        /// </summary>
        /// <param name="i_sourceLoc">The source location</param>
        /// <returns>All the locations that the player can go to them in the board</returns>
        private List<Location> getThePossibleDestLocs(Location i_sourceLoc)
        {
            List<Location> listOfPossibleDestLocations = new List<Location>();
            Location primeDiagonalDestLoc;
            Location secondaryDiagonalDestLoc;

            // Go over the 2 Diagonals of the source loc
            for (int direction = -2; direction <= 2; direction++)
            {
                if (direction != 0)
                {
                    primeDiagonalDestLoc = new Location(i_sourceLoc.Row + direction, i_sourceLoc.Column + direction);
                    secondaryDiagonalDestLoc = new Location(i_sourceLoc.Row - direction, i_sourceLoc.Column + direction);

                    if (!IsLocInOffTheBoard(primeDiagonalDestLoc))
                    {
                        listOfPossibleDestLocations.Add(primeDiagonalDestLoc);
                    }

                    if (!IsLocInOffTheBoard(secondaryDiagonalDestLoc))
                    {
                        listOfPossibleDestLocations.Add(secondaryDiagonalDestLoc);
                    }
                }
            }

            return listOfPossibleDestLocations;
        }

        /// <summary>
        /// This method checks if the location given is located on the board
        /// </summary>
        /// <param name="i_Location">Location for check</param>
        /// <returns>True if it is on the board false if not</returns>
        public bool IsLocInOffTheBoard(Location i_Location)
        {
            int checkRow = i_Location.Row;
            int checkCol = i_Location.Column;

            return checkRow <= 0 || checkRow > m_GameData.GameBoard.Height || checkCol <= 0 || checkCol > m_GameData.GameBoard.Width;
        }

        /// <summary>
        /// Forfeits the game in behalf of the current player
        /// </summary>
        public void ForfeitGame()
        {
            m_GameData.GameStatus = eGameStatus.Forfeit;
            updateGameScore();
        }

        /// <summary>
        /// Gets a computer generated move
        /// </summary>
        /// <param name="gamePlayer">The information about the current player</param>
        /// <returns>A legitimate move</returns>
        public GameMove GetComputerMove(GamePlayer gamePlayer)
        {
            GameMove resultMove = null;
            List<GameMove> possibleMoves = new List<GameMove>();
            List<GameMove> possibleJumps = new List<GameMove>();
            Random randomMoveIndex = new Random();

            // Going over all the possible pawns
            for (int currRow = 1; currRow <= m_GameData.GameBoard.Height; currRow++)
            {
                for (int currColumn = 1; currColumn <= m_GameData.GameBoard.Width; currColumn++)
                {
                    // Check non empty cells only with the player's pawns
                    // And get all the possible moves from them
                    if (m_GameData.GameBoard[currColumn, currRow] != null &&
                        m_GameData.GameBoard[currColumn, currRow].Type == gamePlayer.PlayerPawnType)
                    {
                        extractMovesFromLocation(new Location(currRow, currColumn), possibleJumps, possibleMoves);
                    }
                }
            }

            // If jump move was find - pick a random one
            if (possibleJumps.Count > 0)
            {
                resultMove = possibleJumps[randomMoveIndex.Next(0, possibleJumps.Count - 1)];
            }
            else if (possibleMoves.Count > 0)
            {
                resultMove = possibleMoves[randomMoveIndex.Next(0, possibleMoves.Count - 1)];
            }

            return resultMove;
        }

        /// <summary>
        /// This method responsible to initialize the players in the begginig 
        /// of the game
        /// </summary>
        private void initPlayers(eGameType i_GameType)
        {
            // Check if the first player is human or computer
            if (i_GameType == eGameType.HumanVsHuman)
            {
                // Create player 1 and set it to be human type, pown type and name
                m_GameData.Player1 = new GamePlayer("Human Player 1", eGamePawnType.Black, ePlayerType.Human);
            }
            else
            {
                // Create player 1 and set it to be computer type, pown type and name
                m_GameData.Player1 = new GamePlayer("Computer Player 1", eGamePawnType.Black, ePlayerType.Computer);
            }

            // Create player 2 and set it's type, pown type and name
            m_GameData.Player2 = new GamePlayer("Human Player 2", eGamePawnType.White, ePlayerType.Human);

            // Set the current player to be player 1
            m_GameData.CurrentPlayer = m_GameData.Player1;
        }

        /// <summary>
        /// This method switchs between the players
        /// </summary>
        private void changeTheCurrPlayer()
        {
            if (m_GameData.CurrentPlayer == m_GameData.Player1)
            {
                m_GameData.CurrentPlayer = m_GameData.Player2;
            }
            else
            {
                m_GameData.CurrentPlayer = m_GameData.Player1;
            }
        }

        /// <summary>
        /// Make the one move, assuming that this move is legal
        /// </summary>
        /// <param name="i_currMove"></param>
        private void makeMove(GameMove i_currMove)
        {
            // Variables
            Location sourceLoc = i_currMove.SourceCellLoc;
            Location destLoc = i_currMove.DestCellLocation;

            // Put the pawn of the source square in the dest square
            m_GameData.GameBoard[destLoc.Column, destLoc.Row] = m_GameData.GameBoard[sourceLoc.Column, sourceLoc.Row];

            // Make the source pawn to be null
            m_GameData.GameBoard[sourceLoc.Column, sourceLoc.Row] = null;

            // If the move is a jump
            if (i_currMove.ChekMoveType() == Checkers.Common.eMoveType.Jump)
            {
                int jumpRow = (sourceLoc.Row + destLoc.Row) / 2;  // Row of the jumped piece.
                int jumpCol = (sourceLoc.Column + destLoc.Column) / 2;  // Column of the jumped piece.

                // Empties the jump over square - the pawn is eatan
                m_GameData.GameBoard[jumpCol, jumpRow] = null;
            }

            // If the dest loc make the black pawn to be king
            if ((m_GameData.CurrentPlayer.PlayerPawnType == eGamePawnType.Black && destLoc.Row == m_GameData.GameBoard.Height) ||
                (m_GameData.CurrentPlayer.PlayerPawnType == eGamePawnType.White && destLoc.Row == 1))
            {
                m_GameData.GameBoard[destLoc.Column, destLoc.Row].IsKing = true;
            }
        }

        /// <summary>
        /// This method checks if the given move is legal
        /// </summary>
        /// <param name="i_CurrentPlayerMove">THe current move</param>
        /// <returns>True if it is legal false if not</returns>
        private bool isLegalMove(GameMove i_CurrentPlayerMove)
        {
            bool v_IsLegalMove = true;

            // Check if the destination move is empty
            if (m_GameData.GameBoard[i_CurrentPlayerMove.DestCellLocation] == null &&
                m_GameData.GameBoard[i_CurrentPlayerMove.SourceCellLoc] != null)
            {
                // Check if the source location has the type of the current player
                if (m_GameData.GameBoard[i_CurrentPlayerMove.SourceCellLoc].Type == m_GameData.CurrentPlayer.PlayerPawnType)
                {
                    // If its a jump - check if its a legal jump
                    // If its regular - check if its a legal move
                    if (i_CurrentPlayerMove.ChekMoveType() == Common.eMoveType.Jump)
                    {
                        v_IsLegalMove = isLegalJump(i_CurrentPlayerMove);
                    }
                    else if (i_CurrentPlayerMove.ChekMoveType() == Common.eMoveType.RegularMove)
                    {
                        v_IsLegalMove = isLegalRegularMove(i_CurrentPlayerMove);
                    }
                    else
                    {
                        v_IsLegalMove = false;
                    }
                }
                else
                {
                    v_IsLegalMove = false;
                }
            }
            else
            {
                v_IsLegalMove = false;
            }

            return v_IsLegalMove;
        }

        /// <summary>
        /// Checks if a player could have made a jump instead of a given move
        /// </summary>
        /// <param name="gameMove">The move in hand</param>
        /// <returns>True if a jump was possible</returns>
        private bool couldPlayerJumpInstead(GameMove gameMove)
        {
            // Going over all the possible pawns
            bool couldJump = false;
            m_LastBurntMove = null;
            for (int currRow = 1; currRow <= m_GameData.GameBoard.Height && !couldJump; currRow++)
            {
                for (int currColumn = 1; currColumn <= m_GameData.GameBoard.Width && !couldJump; currColumn++)
                {
                    // Check non empty cells only with the player's pawns,
                    if (m_GameData.GameBoard[currColumn, currRow] != null)
                    {
                        if (m_GameData.GameBoard[currColumn, currRow].Type == m_GameData.CurrentPlayer.PlayerPawnType)
                        {
                            // Get all the possible moves
                            Location sourceLocation = new Location(currRow, currColumn);
                            couldJump = tryJump(sourceLocation, out m_LastBurntMove);
                        }
                    }
                }
            }

            return couldJump;
        }

        /// <summary>
        /// Tries to jump from the current location
        /// </summary>
        /// <param name="i_SourceLocation">The source location from which a jump is attemped</param>
        /// <param name="o_jumpMove">The jump move. Null if no jump is possible</param>
        /// <returns>True if any legitimate jump is possible</returns>
        private bool tryJump(Location i_SourceLocation, out GameMove o_jumpMove)
        {
            bool canJump = false;
            o_jumpMove = null;

            // Check the possible moves
            List<Location> possibleMoves = getThePossibleDestLocs(i_SourceLocation);
            foreach (Location currLocation in possibleMoves)
            {
                // If the move is legal jump
                GameMove tempMove = new GameMove(i_SourceLocation, currLocation);
                if (isJumpMove(tempMove))
                {
                    canJump = true;
                    o_jumpMove = tempMove;
                    break;
                }
            }

            return canJump;
        }

        /// <summary>
        /// Checks if a given move is a legitimate jump move
        /// </summary>
        /// <param name="i_Move">The move in hand</param>
        /// <returns>True if the move is a jump move</returns>
        private bool isJumpMove(GameMove i_Move)
        {
            return i_Move.ChekMoveType() == eMoveType.Jump && isLegalMove(i_Move);
        }

        /// <summary>
        /// Check if the player's regular move is legal
        /// </summary>
        /// <param name="i_CurrentPlayerMove"> The current move of the player</param>
        /// <returns>True if legal false if not</returns>
        private bool isLegalRegularMove(GameMove i_CurrentPlayerMove)
        {
            // Variables
            bool v_IsLegalRegularMove = true;

            if (Math.Abs(i_CurrentPlayerMove.SourceCellLoc.Column - i_CurrentPlayerMove.DestCellLocation.Column) != 1)
            {
                // The move is jumping more than one column across
                v_IsLegalRegularMove = false;
            }
            else
            {
                // Check if the player can move in that direction
                v_IsLegalRegularMove = isPlayerCanMove(m_GameData.CurrentPlayer, i_CurrentPlayerMove);
            }

            return v_IsLegalRegularMove;
        }

        /// <summary>
        /// Check if the player - "i_CurrPlayer" can make the move - "PlayerMove"
        /// </summary>
        /// <param name="i_CurrPlayer">Current player</param>
        /// <param name="i_PlayerMove">Current player move</param>
        /// <returns>True if can move false if not</returns>
        private bool isPlayerCanMove(GamePlayer i_CurrPlayer, GameMove i_PlayerMove)
        {
            // Varibles
            bool v_IsPlayerCanMove = true;

            // If the dest location is not empty
            if (m_GameData.GameBoard[i_PlayerMove.DestCellLocation.Column, i_PlayerMove.DestCellLocation.Row] != null)
            {
                v_IsPlayerCanMove = false;
            }
            else
            {
                v_IsPlayerCanMove = checkUpDownMove(i_CurrPlayer, i_PlayerMove);
            }

            return v_IsPlayerCanMove;
        }

        /// <summary>
        /// Checks if move direction on board (up or down on board) is legal to pawn type
        /// </summary>
        /// <param name="i_CurrPlayer">The current player</param>
        /// <param name="i_PlayerMove">The current player's move</param>
        /// <returns>True if player can move false if not</returns>
        private bool checkUpDownMove(GamePlayer i_CurrPlayer, GameMove i_PlayerMove)
        {
            bool v_IsPlayerCanMove = true;
            eGamePawnType sourcePawnType = m_GameData.GameBoard[i_PlayerMove.SourceCellLoc.Column, i_PlayerMove.SourceCellLoc.Row].Type;
            int sourceRow = i_PlayerMove.SourceCellLoc.Row;
            int destRow = i_PlayerMove.DestCellLocation.Row;

            if (i_CurrPlayer.PlayerPawnType == eGamePawnType.Black)
            {
                // If the pawn on the source location is regular black pawn, it can only move down on the board
                if ((sourcePawnType == eGamePawnType.Black) &&
                    ((!m_GameData.GameBoard[i_PlayerMove.SourceCellLoc.Column, i_PlayerMove.SourceCellLoc.Row].IsKing) &&
                    (destRow < sourceRow)))
                {
                    v_IsPlayerCanMove = false;
                }
            }
            else
            {
                // If the player pawns type is white
                // If the pawn on the source location is regular white pawn, it can only move down on the board
                if ((sourcePawnType == eGamePawnType.White) &&
                    ((!m_GameData.GameBoard[i_PlayerMove.SourceCellLoc.Column, i_PlayerMove.SourceCellLoc.Row].IsKing) &&
                    (destRow > sourceRow)))
                {
                    v_IsPlayerCanMove = false;
                }
            }

            return v_IsPlayerCanMove;
        }

        /// <summary>
        /// Check if the player's jump is legal
        /// </summary>
        /// <param name="i_CurrentPlayerMove">Current player move</param>
        /// <returns>True if legal false if not</returns>
        private bool isLegalJump(GameMove i_CurrentPlayerMove)
        {
            // Varibles
            bool v_IsLegalJump = true;

            // If the player can make the jump
            if (isPlayerCanJump(m_GameData.CurrentPlayer, i_CurrentPlayerMove))
            {
                // If the jump make a King this must be the last jump
                if (i_CurrentPlayerMove.isDestMakeKing(m_GameData.GameBoard))
                {
                    v_IsLegalJump = false;
                }
            }
            else
            {
                v_IsLegalJump = false;
            }

            return v_IsLegalJump;
        }

        /// <summary>
        /// Check if the player - "i_CurrPlayer" can make the jump - "currJump"
        /// </summary>
        /// <param name="i_CurrPlayer">Current player move</param>
        /// <param name="i_currJump">The current jump</param>
        /// <returns>True if player can jump false if not</returns>
        private bool isPlayerCanJump(GamePlayer i_CurrPlayer, GameMove i_currJump)
        {
            bool v_IsPlayerCanJump = true;
            int sourceRow = i_currJump.SourceCellLoc.Row;
            int destRow = i_currJump.DestCellLocation.Row;
            int jumpOverRow = (sourceRow + destRow) / 2;
            int jumpOverCol = (i_currJump.SourceCellLoc.Column + i_currJump.DestCellLocation.Column) / 2;
            eGamePawnType sourcePawnType = m_GameData.GameBoard[i_currJump.SourceCellLoc.Column, sourceRow].Type;
            GamePawn jumpOverPawn = m_GameData.GameBoard[jumpOverCol, jumpOverRow];
            GamePawn destPawn = m_GameData.GameBoard[i_currJump.DestCellLocation.Column, destRow];

            // If this is a reasonable jump (the row of the dest is more or less 2 then the source)
            if (i_currJump.ChekMoveType() != Common.eMoveType.Jump)
            {
                v_IsPlayerCanJump = false;
            }

            // If the dest loc square already contains a pawn
            if (destPawn != null)
            {
                v_IsPlayerCanJump = false;
            }

            // Checks if move on board is legal
            v_IsPlayerCanJump = checkUpDownMove(i_CurrPlayer, i_currJump);

            if (v_IsPlayerCanJump)
            {
                if (i_CurrPlayer.PlayerPawnType == eGamePawnType.Black)
                {
                    // If the jump over location doesn't contain white pawn (regular or king)
                    if (jumpOverPawn == null || jumpOverPawn.Type != eGamePawnType.White)
                    {
                        v_IsPlayerCanJump = false;
                    }
                }
                else
                {
                    // If the jump over location doesn't contain black pawn (regular or king)
                    if (jumpOverPawn == null || jumpOverPawn.Type != eGamePawnType.Black)
                    {
                        v_IsPlayerCanJump = false;
                    }
                }
            }

            return v_IsPlayerCanJump;
        }

        /// <summary>
        /// Gets all the possible legitimate moves from the current location
        /// </summary>
        /// <param name="i_SourceLocation">The source location from which the move are mades</param>
        /// <param name="io_JumpMoves">The list to add all the jump moves into</param>
        /// <param name="io_Moves">The list to add all the jump moves into</param>
        private void extractMovesFromLocation(Location i_SourceLocation, List<GameMove> io_JumpMoves, List<GameMove> io_Moves)
        {
            // Get all the possible moves
            List<Location> possibleMoves = getThePossibleDestLocs(i_SourceLocation);
            if (possibleMoves.Count > 0)
            {
                foreach (Location currDestinationLocation in possibleMoves)
                {
                    // Make a move object for the current locations
                    GameMove tempMove = new GameMove(i_SourceLocation, currDestinationLocation);

                    // Add the move to the proper list (Jump/Regular Move)
                    if (isJumpMove(tempMove))
                    {
                        io_JumpMoves.Add(tempMove);
                    }
                    else if (isLegalMove(tempMove))
                    {
                        io_Moves.Add(tempMove);
                    }
                }
            }
        }
    }
}
